/*=====================================================================================
* Copyright (c) 2020, micROS Group, NIIDT, TAIIC.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
*  provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and
*      the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
*      and the following disclaimer in the documentation and/or other materials provided with the
*      distribution.
* 3. All advertising materials mentioning features or use of this software must display the following
*      acknowledgement: This product includes software developed by the micROS Group and its
*      contributors.
* 4. Neither the name of the Group nor the names of contributors may be used to endorse or promote
*     products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY MICROS GROUP AND CONTRIBUTORS ''AS IS''AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR  PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL THE MICROS, GROUP OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
*  SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
*  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION). HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
=======================================================================================
*/

#include "yolov3_clip_detector.h"
#include <algorithm>
#include "acl/acl.h"
#include <dirent.h>
#include <math.h>
#include <cstdlib>
#include <chrono>
#include <unistd.h>


void DestroyResource()
{
    aclError ret = aclFinalize();
    if (ret != ACL_SUCCESS) {
      std::cout<<"execute  aclFinalize false"<<std::endl;
    }
}

Yolov3CLIPDetector::Yolov3CLIPDetector()
{
  std::cout<<"-- Creat instence of Resource Yolov3CLIPDetector"<<std::endl;
}

Yolov3CLIPDetector::~Yolov3CLIPDetector()
{
  aclmdlDestroyDesc(modelDesc);
  aclmdlUnload(modelId);
  
  aclrtFree(pictureDeviceData);
  pictureDeviceData = nullptr;
  aclDestroyDataBuffer(inputBuffer);
  inputBuffer = nullptr;
  aclmdlDestroyDataset(input);
  input = nullptr;

  aclDestroyDataBuffer(outputData);
  outputData = nullptr;
  aclrtFree(outputDeviceData);
  outputDeviceData = nullptr;
  aclmdlDestroyDataset(output);
  output = nullptr;
  aclError ret = aclrtResetDevice(deviceId);
  DestroyResource();
  DestroyInput();
}

void Yolov3CLIPDetector::DestroyInput()
{
    if (input == nullptr) {
        return;
    }

    aclrtFree(pictureDeviceData);
    pictureDeviceData = nullptr;
    aclDestroyDataBuffer(inputBuffer);
    inputBuffer = nullptr;
    aclmdlDestroyDataset(input);
    input = nullptr;

    std::cout<<"destroy model input success"<<std::endl;
}


  bool Yolov3CLIPDetector::initEngine(const std::string& modelPath){
    aclError ret = aclInit(nullptr);
    if (ret != ACL_SUCCESS) {
        std::cout<<"acl init false, errorCode = "<< static_cast<int32_t>(ret)<<std::endl;
        return false;
    }
    std::cout<<"acl init success"<<std::endl;

    ret = aclrtSetDevice(deviceId);
    if (ret != ACL_SUCCESS) {
        std::cout<<"acl set device "<<deviceId<<" false, errorCode = "<< static_cast<int32_t>(ret)<<std::endl;
        return false;
    }
    std::cout<<"set device "<<deviceId<<" success"<<std::endl;

    // get run mode
    ret = aclrtGetRunMode(&runMode);
    if (ret != ACL_SUCCESS) {
        std::cout<<"acl get run mode false, errorCode = "<<static_cast<int32_t>(ret)<<std::endl;
        return false;
    }
    std::cout<<"get run mode success"<<std::endl;

    // model init
    ret = aclmdlLoadFromFile(modelPath.c_str(), &modelId);
    if (ret != ACL_SUCCESS) {
        std::cout<<"execute LoadModel false, errorCode ="<<static_cast<int32_t>(ret)<<std::endl;
        return false;
    }

    modelDesc = aclmdlCreateDesc();
    if (modelDesc == nullptr) {
        std::cout<<"create model description failed"<<std::endl;
        return false;
    }

    ret = aclmdlGetDesc(modelDesc, modelId);
    if (ret != ACL_SUCCESS) {
        std::cout<<"get model description failed, modelId is "<<modelId<< ", errorCode is "<<static_cast<int32_t>(ret)<<std::endl;
        return false;
    }

    std::cout<<"create model description success"<<std::endl;

  

    // cteate output
    output = aclmdlCreateDataset();
    if (output == nullptr) {
        std::cout<<"can't create dataset, create output false"<<std::endl;
        return false;
    }
    modelOutputSize = aclmdlGetOutputSizeByIndex(modelDesc, 0);
    std::cout<<"output size: "<<modelOutputSize<<std::endl;
    ret = aclrtMalloc(&outputDeviceData, modelOutputSize, ACL_MEM_MALLOC_NORMAL_ONLY);
    if (ret != ACL_SUCCESS) {
      std::cout<<"can't malloc buffer, size is"<< modelOutputSize<<"create output false, errorCode is "<<static_cast<int32_t>(ret)<<std::endl;
        return false;
    }

    outputData = aclCreateDataBuffer(outputDeviceData, modelOutputSize);
    std::cout<<"modelOutputSize:"<<modelOutputSize<<std::endl;
    if (outputData == nullptr) {
        std::cout<<"can't create data buffer, create output false"<<std::endl;
        (void)aclrtFree(outputDeviceData);
        return false;
    }

    ret = aclmdlAddDatasetBuffer(output, outputData);
    if (ret != ACL_SUCCESS) {
        std::cout<<"can't add data buffer, create output false, errorCode is "<<static_cast<int32_t>(ret)<<std::endl;
        (void)aclrtFree(outputDeviceData);
        (void)aclDestroyDataBuffer(outputData);
        return false;
    }
    std::cout<<"create model output success"<<std::endl;

    ret = aclrtMallocHost(&outputHostData, modelOutputSize);
    if (ret != ACL_SUCCESS) {
       std::cout<<"can't malloc outputHostData buffer, errorCode is "<<static_cast<int32_t>(ret)<<std::endl;
        return false;
    }

    fileSize = clipWidth  * clipHeight * 3;
    if (pictureDeviceData == nullptr)
    {
        ret = aclrtMalloc(&pictureDeviceData, fileSize * 4, ACL_MEM_MALLOC_HUGE_FIRST);
    }

    input = aclmdlCreateDataset();
    for(size_t index = 0; index < aclmdlGetNumInputs(modelDesc); ++index)
    {
        const char* name = aclmdlGetInputNameByIndex(modelDesc, index);
        size_t inputLen = aclmdlGetInputSizeByIndex(modelDesc, index);
	      std::cout<<"input name: "<<name<<", length: "<<inputLen<<std::endl;
        if(strcmp(name, ACL_DYNAMIC_TENSOR_NAME) == 0)
        {
            void *data = nullptr;
            ret = aclrtMalloc(&data, inputLen, ACL_MEM_MALLOC_HUGE_FIRST);
            batchBuffer = aclCreateDataBuffer(data, inputLen);
            ret = aclmdlAddDatasetBuffer(input, batchBuffer);
        }else{
            inputBuffer = aclCreateDataBuffer(pictureDeviceData, fileSize * 4);
            ret = aclmdlAddDatasetBuffer(input, inputBuffer);
        }
    }
    std::cout<<"create model input success"<<std::endl; 
    return true;
  }

 /**
 * @brief The interface class of init
 * @param resource_id
 * @return true or false
 * 
 */  
  bool Yolov3CLIPDetector::Initialize(const std::string& modelPath)
  {
    aclError ret;
    bool flag = initEngine(modelPath);
    if (!flag){
      std::cout<<"init false"<<std::endl;
      return false;
    }
    
    return true;
  }


bool Yolov3CLIPDetector::engineInfer(std::vector<float>&input_data, uint32_t fileSize){
  aclError ret; 
  std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();
  if (runMode == ACL_HOST) {
      ret = aclrtMemcpy((char *)pictureDeviceData, fileSize * 4, \
      input_data.data(), fileSize * 4 , ACL_MEMCPY_HOST_TO_DEVICE);
  }
  else
  {
      ret = aclrtMemcpy((char *)pictureDeviceData, fileSize * 4, \
      input_data.data(), fileSize * 4, ACL_MEMCPY_DEVICE_TO_DEVICE);
  }
  std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now();
  std::chrono::duration<double, std::milli> time_span = t2 - t1;
  std::cout << "Memcpy took " << time_span.count() << " milliseconds."<<std::endl;
  
  std::chrono::high_resolution_clock::time_point t11 = std::chrono::high_resolution_clock::now();
  ret = aclmdlExecute(modelId, input, output);
  std::chrono::high_resolution_clock::time_point t22 = std::chrono::high_resolution_clock::now();
  std::chrono::duration<double, std::milli> time_span2 = t22 - t11;
  std::cout << "Execute took " << time_span2.count() << " milliseconds."<<std::endl;
  if (ret != ACL_SUCCESS) {
      std::cout<<"execute aclmdlExecute false "<<static_cast<int32_t>(ret)<<std::endl;
      return false;
  }
  std::cout<<"excute success "<<std::endl;
  return true;
}

  bool Yolov3CLIPDetector::inference(cv::Mat &img)
  {
    aclError ret;
    std::vector<float>input_data;
    image_preprocess(img, input_data);
    float* outFloatData;
    bool flag = engineInfer(input_data, fileSize);
    if (!flag){
      std::cout<<"excute engineInfer false"<<std::endl;
    }
    if (runMode == ACL_HOST) {
        aclError ret = aclrtMallocHost(&outputHostData, modelOutputSize);
        ret = aclrtMemcpy(outputHostData, modelOutputSize, outputDeviceData, modelOutputSize, ACL_MEMCPY_DEVICE_TO_HOST);
        outFloatData = reinterpret_cast < float * > (outputHostData);
    }
    else
    {
        outFloatData = reinterpret_cast < float * > (outputDeviceData);
    }
    std::vector<float> embeding(outFloatData, outFloatData + modelOutputSize / 4);
    std::cout<<"modelOutputSize / 4 = "<<modelOutputSize / 4<<std::endl;
    for (size_t i = 0; i<6; i++){
      std::cout<<"i: "<<i<<", val: "<<embeding[i]<<std::endl; 
    }
    return true;

}
  

  void Yolov3CLIPDetector::image_preprocess(cv::Mat &frame, std::vector<float>&input_data) {  
      cv::Mat src_temp, dst_temp, dst_image;
      // 1. resize by CUBIC mode
      cv::resize(frame, src_temp, cv::Size(clipWidth, clipHeight), 0, 0, cv::INTER_CUBIC);
      src_temp.convertTo(dst_temp, CV_32F);
      dst_temp.copyTo(dst_image);

    // 2. convert BGR to RGB
    cv::cvtColor(dst_image, dst_image, cv::COLOR_BGR2RGB);
    for (int i = 0; i < dst_image.rows; i++) {
        for (int j = 0; j < dst_image.cols; j++) {
            cv::Vec3f buf = dst_image.at<cv::Vec3f>(i,j);
            buf[0] = ((buf[0] / 255.0f) - means[0]) / stds[0];
            buf[1] = ((buf[1] / 255.0f) - means[1]) / stds[1];
            buf[2] = ((buf[2] / 255.0f) - means[2]) / stds[2];
            dst_image.at<cv::Vec3f>(i,j) = buf;
        }
    }

      cv::Mat channels[3];
      cv::split(dst_image, channels);
      for (int i=0; i<dst_image.channels(); i++){
          std::vector<float> data = std::vector<float>(channels[i].reshape(1, dst_image.cols * dst_image.rows));
          input_data.insert(input_data.end(), data.begin(), data.end());
        }
  }

  std::vector<float> Yolov3CLIPDetector::softmax(std::vector<float> &conf, float temperature)
  {
    for (auto &value : conf)
    {
      value *= temperature;
    }

    std::vector<float>::iterator biggest = std::max_element(conf.begin(),conf.end());
    float max_value = *biggest;
    
    float total = 0;
    for (auto x : conf)
    {
      total += exp(x-max_value);
    }

    std::vector<float> results;
    for (auto x : conf)
    {
      results.push_back(exp(x-max_value)/total);
    }
    return results;
  }
  

